package ar.edu.unq.leezsi.core;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.FilterChain;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import ar.edu.unq.leezsi.html.Component;
import ar.edu.unq.leezsi.html.Page;
import ar.edu.unq.leezsi.requestcycle.RequestCycle;
import ar.edu.unq.leezsi.utils.ClassWrapper;
import ar.edu.unq.leezsi.utils.CookieUtils;
import ar.edu.unq.leezsi.utils.RandomUtils;

public class CowebApplication implements Serializable {
	private static final long serialVersionUID = -762916281547989949L;

	private static final String CONTEXT_CONSTANT = "cwctx";

	public synchronized static CowebApplication get() {
		return instance;
	}

	private static void setApplication(final CowebApplication application) {
		instance = application;
	}

	private URLMapper URLMapper = new SimpleURLMapper();
	private final Map<String, Context> contexts = new HashMap<String, Context>();

	private static CowebApplication instance;

	private final List<Injector> injectors = new ArrayList<Injector>();
	private final List<SecurityPolicy> securityListeners = new ArrayList<SecurityPolicy>();

	public CowebApplication() {
		setApplication(this);
		System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
		System.out.println("@@@@@@@@@ COWEB-MOTION APPLICATION @@@@@@@@");
		System.out.println("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
	}

	public void applySecurityInstantiationPolicy(final Component<?> component) {
		for (SecurityPolicy policy : this.securityListeners()) {
			policy.applyOnInstation(component);
		}
	}

	public void applySecurityPreOnBeforeRendererPolicy(
			final Component<?> component) {
		for (SecurityPolicy policy : this.securityListeners()) {
			policy.preOnBeforeRender(component);
		}

	}

	public void destroy() {

	}

	public synchronized void doFilter(final HttpServletRequest request,
			final HttpServletResponse response, final FilterChain chain) {
		CookieUtils cookieUtils = new CookieUtils(request);
		HttpSession session = request.getSession(true);

		this.getOrCreateContext(cookieUtils);
		this.getRequestCycle(this.getContext(session)).doFilter(request,
				response, chain);
	}

	protected Class<Page<?>> errorPageClass() {
		return null;
	}

	private Context getContext(HttpSession session) {
		if (session.isNew()) {
			Context context = new Context(session.getId(), this.getURLMapper());
			session.setAttribute(CONTEXT_CONSTANT, context);
			return context;
		}
		return (Context) session.getAttribute(CONTEXT_CONSTANT);
	}

	public Map<String, Context> getContexts() {
		return this.contexts;
	}

	public synchronized Page<?> getErrorPage() {
		return new ClassWrapper<Page<?>>(this.errorPageClass()).newInstance();
	}

	private Context getOrCreateContext(CookieUtils cookieUtils) {
		Cookie cookie = cookieUtils.getFromCookieOrRequest(CONTEXT_CONSTANT);
		String id = cookie == null ? RandomUtils.getLongestId() : cookie
				.getValue();
		if (this.getContexts().containsKey(id)) {
			return this.getContexts().get(id);
		} else {
			Context ctx = new Context(id, this.getURLMapper());
			if (cookieUtils.contains(CONTEXT_CONSTANT)) {
				ctx.fromCookie(true);
			} else {
				ctx.fromCookie(false);
			}
			this.getContexts().put(id, ctx);
			return ctx;
		}
	}

	public RequestCycle getRequestCycle(Context context) {
		return context.getRequestCycle();
	}

	private final URLMapper getURLMapper() {
		return this.URLMapper;
	}

	public void inject(final Component<?> component) {
		for (Injector injector : this.injectorListeners()) {
			injector.inject(component);
		}
	}

	private List<Injector> injectorListeners() {
		return this.injectors;
	}

	public void mapURL(String url, Class<? extends Page<?>> page,
			Object... params) {
		this.getURLMapper().map(url, page, params);
	}

	public void mapURL(String url, Page<?> page) {
		this.getURLMapper().map(url, page);
	}

	private List<SecurityPolicy> securityListeners() {
		return this.securityListeners;
	}

	public Class<Session> sessionClass() {
		return Session.class;
	}

	public final void setURLMapper(URLMapper uRLMapper) {
		this.URLMapper = uRLMapper;
	}

}
